
static char RCSId[]="$Id: PlotView.m,v 1.1.1.1 1993/03/18 03:34:58 davis Exp $";


#import <appkit/Application.h>
#import <appkit/ClipView.h>
#import <appkit/Font.h>
#import <appkit/FontManager.h>
#import <appkit/Matrix.h>
#import <appkit/MenuCell.h>
#import <appkit/NXImage.h>
#import <appkit/Pasteboard.h>
#import <appkit/SavePanel.h>
#import <appkit/Text.h>

#import <defaults/defaults.h>	/* NXGetTempFilename()	*/

#import <dpsclient/psops.h>
#import <libc.h>		/* unlink(), MAXPATHLEN	*/

#import "GnuplotPlot.h"
#import "PlotView.h"



/*  Does the list of NXAtoms "types" include the NXAtom "type"?  */
static BOOL includesType(NXAtom *types, NXAtom type)
{
    while (types && *types) {
        if (type == *types) return YES;
        types++;
    }
    return NO;
}


@implementation PlotView


- initFrame:(const NXRect *)frameRect;
{
    const char *validSendTypes[2];

    [super initFrame:frameRect];
    [self setClipping: NO];	/* Because we're in a ClipView already	*/
    [self drawInSuperview];	/* Use our ClipView's coordinate system	*/

    fullPath = NULL;
    epsStream = NULL;
    image = nil;
    tempFileNeedsUpdate = YES;

    /* Setup services */
    validSendTypes[0] = NXPostScriptPboardType;
    validSendTypes[1] = NXTIFFPboardType;
    validSendTypes[2] = NULL;
    [NXApp registerServicesMenuSendTypes: validSendTypes andReturnTypes: NULL];

    return self;
}


- (BOOL) acceptsFirstResponder
{
    return YES;			/* So the font can be changed with fontpanel */
}


- (BOOL)acceptsFirstMouse
{
    return YES;			/* So we can drag the EPS out as a file	*/
}



- changeFont:sender
{
    id newfont;
    id doc;

    newfont = [sender convertFont: [(doc = [window delegate]) currentFont]];
    [doc setCurrentFont:newfont];

    return self;
}


- copyFont:sender
{
    /* 
     *  This method is invoked when a user presses the "Copy Font" 
     *  menu button.  We're supposed to put the current font on the 
     *  font pasteboard, and the easiest way to do that is to take 
     *  advantage of the Text class's ability to do that.  Every step 
     *  here is necessary:  we create a Text object, set it to RTF 
     *  (non-monofont), make a selection (even though there is no text 
     *  to select, there must be a selection), set the font to the 
     *  doc's current font, tell the text object to copy the font to 
     *  the font pasteboard, and then free the text object.
     */
    [[[[[[[Text allocFromZone: [self zone]] init]
		  setMonoFont: NO]
		       setSel: 0:1]
		      setFont: [[window delegate] currentFont]]
		     copyFont: sender]
		         free];

    return self;
}


- pasteFont:sender
{
    Text *text = [[Text allocFromZone: [self zone]] init];

    /* 
     *  We must specify that the text is not monofont (i.e. is RTF) 
     *  and there must be a selection in order to paste.  There 
     *  doesn't need to be any text.
     */
    [[text setMonoFont:NO] setSel:0 :1];

    if ([text pasteFont:sender])	/* Returns nil if there's trouble */
	[[window delegate] setCurrentFont:[text font]];

    [text free];
    return self;
}



- validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
/*
 *  Services menu support.
 *  We are a valid requestor if the send type is EPS and there is no 
 *  return data from the request.
 */
{
    if (image &&
	((sendType == NXPostScriptPboardType) ||
	 (sendType == NXTIFFPboardType)) &&
	(!returnType || !*returnType))

	return self;

    return [superview validRequestorForSendType:sendType
				  andReturnType:returnType];
}



- (BOOL) writeSelectionToPasteboard:pboard types:(NXAtom *)types
/*
 *  Services menu support.
 *  Here we are asked by the Services menu mechanism to supply
 *  the image data (which we said we were a valid requestor for
 *  in the above method).
 */
{
    while (types && *types) {
        if ((*types == NXPostScriptPboardType) || (*types == NXTIFFPboardType))
	    break;
        types++;
    }

    if (types && *types && [self copyToPasteboard:pboard types:types]) {
        return YES;
    } else {
        return NO;
    }
}


- (BOOL)validateCommand:menuCell
{
    SEL action = [menuCell action];

    if (action == @selector(copy:))
        return image? YES :NO;
    else if (action == @selector(copyFont:)) {
	[[FontManager new] setEnabled:image? YES : NO];
        return image? YES :NO;
    } else if (action == @selector(pasteFont:))
        return image? YES :NO;
    else if (action == @selector(saveAsEPS:))
	return epsStream? YES :NO;

    return YES;
}



- copy:sender
{
    return [self copyToPasteboard:
			[Pasteboard newName:(const char *)NXSelectionPboard]];
}



- copyToPasteboard:pboard
{
    return [self copyToPasteboard:pboard types:NULL];
}
    



- copyToPasteboard:pboard types:(NXAtom *)typesList
{
    char *data;
    NXStream *stream;   
    const char *types[1];
    int length, maxlen;

    if (image) {
	if (!typesList || includesType(typesList, NXPostScriptPboardType))  {
	    types[0] = NXPostScriptPboardType;
	    stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	    [self copyPSToStream:stream];
	    NXGetMemoryBuffer(stream, &data, &length, &maxlen);
	    [pboard declareTypes:types num:1 owner:[self class]];
	    [pboard writeType:NXPostScriptPboardType data:data length:length];
	    NXCloseMemory(stream, NX_FREEBUFFER);

	    return self;

	} else if (typesList && includesType(typesList, NXTIFFPboardType)) { 
	    types[0] = NXTIFFPboardType;
	    stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	    [self copyTIFFToStream:stream];
	    NXGetMemoryBuffer(stream, &data, &length, &maxlen);
	    [pboard declareTypes:types num:1 owner:[self class]];
	    [pboard writeType:NXTIFFPboardType data:data length:length];
	    NXCloseMemory(stream, NX_FREEBUFFER);

	    return self;

	}
    }

    return nil;
}


- copyPSToStream:(NXStream *)stream
{
    char *streambuf;
    int len, maxlen;

    if (image) {

	NXGetMemoryBuffer(epsStream, &streambuf, &len, &maxlen);
	NXPrintf (stream, "%s", streambuf);

	return self;

    }

    return nil;
}



- copyTIFFToStream:(NXStream *)stream
{
    if (image) {
	[image writeTIFF:stream];
	return self;
    }

    return nil;
}



- saveAsEPS:sender
{
    id savePanel;

    savePanel = [SavePanel new];
    [savePanel setRequiredFileType:"eps"];
    [savePanel runModal];
    [self saveEPSToFile:[savePanel filename]];

    return self;
}



- (const char *) writeEPSToTempFile
{
    const char *realName;
    char *cur;
    int pos;

    /*  
     *  If epsStream has been written to a temp file before, and the 
     *  epsStream hasn't changed since then, we don't need to write 
     *  the temp file again.  This checks to see if the stream has 
     *  changed.  It also should (but doesn't) check to see if the 
     *  temp file still exists and is accessable (readable).
     */
    if (!tempFileNeedsUpdate)
	return fullPath;
    else if (!epsStream) {
	[self unlinkFile];
	return NULL;
    }

    [self unlinkFile];		/* Get rid of previous temp file, if any */
    fullPath = (char *) NXZoneMalloc ([self zone], MAXPATHLEN * sizeof (char));
    *fullPath = '\0';

    /*  
     *  We need to come up with a unique name for the temp file, which 
     *  will reside in /tmp.  We use the name of the plot and change 
     *  it with NXGetTempFilename.
     */
    realName = [[window delegate] name];
    if (cur = rindex(realName, '/')) {	/* The plot has been titled	*/

	if (++cur)
	    sprintf (fullPath, "/tmp/%s", cur);
	else
	    return NULL;		/* ...ERROR: path ends in slash	*/

    } else				/* The plot is untitled		*/

	sprintf (fullPath, "/tmp/%s", realName);


    pos = strlen (fullPath);

    if (cur = rindex (fullPath, '.')) {	/* The name has an extension	*/
	pos -= strlen (cur);		/* Replace it with .eps		*/
	strcpy (cur, "xxxxxx.eps");
    } else				/* The name has no extension	*/
	strcat (fullPath, "xxxxxx.eps");


    NXGetTempFilename(fullPath, pos);	/* Make sure filename is unique	*/
    [self saveEPSToFile:fullPath];	/* Save the stream to the file	*/

    tempFileNeedsUpdate = NO;
    return fullPath;
}


- saveEPSToFile:(const char *)filename
{
    /*  
     *  Saves the epsStream to a file as Encapsulated PostScript.  
     *  Assumes filename is a validly formed, unique pathname.  (It 
     *  will overwrite the file if filename is not unique.)
     */
    
    NXStream *newStream;
    char *buf;
    int bufsize, bufmax;
    
// See Status.m for description of problem with NXSaveToFile().
//    NXSaveToFile (epsStream, filename);

    newStream = NXOpenMemory (NULL, 0, NX_WRITEONLY);
    NXGetMemoryBuffer (epsStream, &buf, &bufsize, &bufmax);
    NXPrintf (newStream, "%s", buf);
    NXFlush (newStream);
    NXSaveToFile (newStream, filename);
    NXCloseMemory (newStream, NX_FREEBUFFER);

    return self;
}



- unlinkFile
{
    if (fullPath)  {
	if (*fullPath)
	    unlink (fullPath);
	NXZoneFree ([self zone], fullPath);
	fullPath = NULL;
    }

    return self;
}



- mouseDown:(NXEvent *)theEvent
{
    NXRect	fileRect = {{theEvent->location.x - 24.0,
			     theEvent->location.y - 24.0}, {48.0, 48.0}};

    if ([self writeEPSToTempFile])  {
	[[[window contentView] superview] convertRect:&fileRect toView:self];

	[self dragFile:fullPath
              fromRect:&fileRect
             slideBack:YES
                 event:theEvent];
    
	return self;
    } else
	return nil;
}


		
/*
 *  Redisplays the contents of the view by compositing the image onto 
 *  the view.
 */
- drawSelf:(NXRect *)r :(int) count
{
    PSsetgray(NX_WHITE);	/* For color machines, we must clear	*/
    NXRectFill(&(r[0]));	/* the window to white.			*/

    if (image)
	[image composite:NX_SOVER fromRect:&(r[0]) toPoint:&(r[0].origin)];

    return self;
}


- newStream:(NXStream *) aStream
{
    NXSize imageSize;

    if (image)
	[image free];

    if (aStream)  {

	NXSeek(aStream, 0, NX_FROMSTART);
	image = [[[[NXImage allocFromZone: [self zone]]
			   initFromStream: aStream]
			      setScalable: YES]
			  setDataRetained: YES];

	[image getSize:&imageSize];
	imageOrigWidth = imageSize.width;
	imageOrigHeight = imageSize.height;

	NX_WIDTH(&bounds) = NX_WIDTH(&frame) = imageSize.width;
	NX_HEIGHT(&bounds) = NX_HEIGHT(&frame) = imageSize.height;
	epsStream = aStream;

    } else {
	NX_WIDTH(&bounds) = NX_HEIGHT(&bounds) = NX_WIDTH(&frame) =
	    NX_HEIGHT(&frame) = 0;
	imageOrigWidth = imageOrigHeight = 0;
	image = nil;
	epsStream = NULL;
    }

    tempFileNeedsUpdate = YES;

    return self;
}





/*  
 *  This method, in addition to freeing the image, unlinks the 
 *  temporary EPS file if it exists.
 */
- free
{
    [self unlinkFile];

    if (image)
	[image free];

    return [super free];
}


/*
 *  This method takes only one scale factor because we never want the 
 *  view to have different proportions than the image.  Scale factor 
 *  of zero means size to window (so that the entire plot is visible 
 *  in the window).
 */
- (float)scale:(float)scaleFactor
{
    float	factor;
    NXSize	imageSize;
    NXRect	ourRect;

    if (image)  {

	if (scaleFactor)  {

	    factor = scaleFactor;

	} else  {

	    [superview getDocVisibleRect: &ourRect];
	    factor = ourRect.size.width / imageOrigWidth;
	    if ((imageOrigHeight * factor) > ourRect.size.height)
		factor = ourRect.size.height / imageOrigHeight;

	}

	imageSize.width = imageOrigWidth * factor;
	imageSize.height = imageOrigHeight * factor;
	[image setSize:&imageSize];

	/* Show the newly sized image. */

	NX_WIDTH(&bounds) = NX_WIDTH(&frame) = imageSize.width;
	NX_HEIGHT(&bounds) = NX_HEIGHT(&frame) = imageSize.height;
	[superview descendantFrameChanged:self];

	[self display];
	return factor;

    } else
	return 0.0;
}


// Shuts up the compiler about unused RCSId
- (const char *) rcsid
{
    return RCSId;
}


@end
